//
// Created by Tony on 2024/5/19.
//

#include "Lexer.h"

#include "../common/KeyWords.h"
#include "../common/Utils.h"
#include "token/BooleanToken.h"
#include "token/CharToken.h"
#include "token/EofToken.h"
#include "token/Flt64Token.h"
#include "token/Int64Token.h"
#include "token/NameToken.h"
#include "token/NullToken.h"
#include "token/PunctToken.h"
#include "token/StringToken.h"
#include <memory>

namespace Lexer {

using std::make_shared;

bool Lexer::parseString(const string& text, uint16_t lineNo, TokenQueue& tokenQueue) {
    this->text = text;
    this->lineNo = lineNo;
    this->cursor = 0;

    while (true) {
        shared_ptr<Token> token;
        auto result = parseToken(token);
        CHECK_FALSE_RETURN(result, false);
        if (token != nullptr) {
            tokenQueue.offer(token);
        }
        if (cursor >= this->text.size()) {
            return true;
        }
    }
}

bool Lexer::parseFile(const shared_ptr<SourceFile>& file, TokenQueue& tokenQueue) {
    uint16_t index = 0;

    for (const auto& line : file->getLines()) {
        auto result = parseString(line, ++index, tokenQueue);
        CHECK_FALSE_RETURN(result, false);
    }

    tokenQueue.offer(make_shared<EofToken>());
    return true;
}

bool Lexer::parseToken(shared_ptr<Token>& token) {
    char ch;
    while (true) {
        if (cursor == text.size()) {
            return true;
        }

        ch = getChar();
        if (!isspace(ch)) { // skip space
            break;
        }
    }

    if (isalpha(ch)) {
        unGetChar();
        Position position{.lineNo = lineNo, .start = cursor};

        string identifier;
        bool result = parseIdentifier(identifier);
        CHECK_FALSE_RETURN(result, false);

        position.length = identifier.size();

        if (identifier == KeyWords::TRUE) {
            token = make_shared<BooleanToken>(position, true);
        } else if (identifier == KeyWords::FALSE) {
            token = make_shared<BooleanToken>(position, false);
        } else if (identifier == KeyWords::NIL) {
            token = make_shared<NullToken>(position);
        } else if (identifier == KeyWords::NEW) {
            token = make_shared<PunctToken>(position, 'n', 'e', 'w');
        } else {
            token = make_shared<NameToken>(position, identifier);
        }

        return true;
    }

    if (isdigit(ch)) {
        unGetChar();
        return parseNumber(token);
    }

    if (ch == '\"') {
        unGetChar();
        return parseStringLiteral(token);
    }

    if (ch == '\'') {
        unGetChar();
        return parseCharLiteral(token);
    }

    if (ch == '/' && cursor < text.size()) {
        if (getChar() == '/') {
            skipComment();
            return true;
        }
        unGetChar();
    }

    if (ispunct(ch)) {
        unGetChar();
        return parsePunct(token);
    }

    setErrMsg("unknown character <%s>, at <%d:%d>", ch, lineNo, cursor - 1);
    return false;
}

char Lexer::getChar() { return text[cursor++]; }

void Lexer::unGetChar() { cursor--; }

void Lexer::skipSpace() {
    while (true) {
        char ch = getChar();
        if (!isspace(ch)) {
            break;
        }
    }
    unGetChar();
}

void Lexer::skipComment() {
    while (true) {
        char ch = getChar();
        if (ch == '\n' || cursor == text.size()) {
            return;
        }
    }
}

bool Lexer::parseIdentifier(string& identifier) {
    uint16_t i = 0;
    for (; i <= MAX_NAME_LEN; i++) {
        char ch = getChar();
        if (isalpha(ch) || isdigit(ch) || ch == '_') {
            identifier.push_back(ch);
        } else {
            unGetChar();
            break;
        }
    }

    if (i > MAX_NAME_LEN) {
        setErrMsg("name length greater than %d, at <%d:%d>", MAX_NAME_LEN, lineNo,
                  cursor - MAX_NAME_LEN);
        return false;
    }
    return true;
}

bool Lexer::parseNumber(shared_ptr<Token>& token) {
    Position position{.lineNo = lineNo, .start = cursor, .length = 0};

    int64_t i64 = getChar() - '0';
    position.length++;

    char ch;
    while (true) {
        ch = getChar();
        position.length++;

        if (isdigit(ch)) {
            i64 = i64 * 10 + (ch - '0'); // TODO 处理溢出的问题
        } else if (ch == '.') {
            break;
        } else {
            unGetChar();
            position.length--;
            token = make_shared<Int64Token>(position, i64);
            return true;
        }
    }

    auto fp64 = static_cast<double>(i64);
    double ratio = 0.1;
    while (true) {
        ch = getChar();
        position.length++;

        if (isdigit(ch)) {
            fp64 += static_cast<double>(ch - '0') * ratio; // TODO 处理溢出的问题
            ratio *= 0.1;
        } else {
            unGetChar();
            position.length--;
            token = make_shared<Flt64Token>(position, fp64);
            return true;
        }
    }
}

bool Lexer::parseStringLiteral(shared_ptr<Token>& token) {
    Position position{.lineNo = lineNo, .start = cursor, .length = 0};

    getChar(); // 跳过开头的 "
    position.length++;

    string str;
    while (true) {
        char ch = getChar();
        position.length++;

        if (ch == '\n') {
            setErrMsg("invalid end of string literal, at %s", position.endStr().c_str());
            return false;
        }

        if (ch == '\"') {
            break;
        }

        if (ch == '\\') { // 转义字符
            ch = getChar();
            position.length++;

            if (ch == '\n') {
                setErrMsg("invalid end of string literal, at %s", position.endStr().c_str());
                return false;
            }

            switch (ch) {
                case 'n':
                    str.push_back('\n');
                    break;
                case 't':
                    str.push_back('\t');
                    break;
                case '\\':
                case '"':
                    str.push_back(ch);
                    break;
                default:
                    setErrMsg("invalid character in string literal, at %s",
                              position.endStr().c_str());
                    return false;
            }
        } else {
            str.push_back(ch);
        }
    }

    token = make_shared<StringToken>(position, str);
    return true;
}

bool Lexer::parseCharLiteral(shared_ptr<Token>& token) {
    Position position{.lineNo = lineNo, .start = cursor, .length = 0};

    getChar(); // 跳过开头的 '
    position.length++;

    char value;

    char ch = getChar();
    position.length++;

    if (ch == '\\') { // '\t', '\n', '\'', '\\'
        ch = getChar();
        position.length++;

        switch (ch) {
            case 't':
                value = '\t';
                break;
            case 'n':
                value = '\n';
                break;
            case '\'':
            case '\\':
                value = ch;
                break;
            default:
                setErrMsg("invalid character literal, at %s", position.endStr().c_str());
                return false;
        }
    } else { // example: 'a'
        value = ch;
    }

    ch = getChar();
    position.length++;

    if (ch != '\'') {
        setErrMsg("invalid end of character literal, at %s", position.endStr().c_str());
        return false;
    }

    token = make_shared<CharToken>(position, value);
    return true;
}

bool Lexer::parsePunct(shared_ptr<Token>& token) {
    Position position = {.lineNo = lineNo, .start = cursor, .length = 0};

    char ch1 = getChar();
    position.length++;

    char ch2 = getChar();
    position.length++;

    switch (ch1) {
        case '+':
            if (ch2 == '+') {
                token = make_shared<PunctToken>(position, ch1, ch2);
                return true;
            }
            break;
        case '-':
            if (ch2 == '-') {
                token = make_shared<PunctToken>(position, ch1, ch2);
                return true;
            }
            break;
        case '!':
        case '=':
            if (ch2 == '=') {
                token = make_shared<PunctToken>(position, ch1, ch2);
                return true;
            }
            break;
        case '>':
            if (ch2 == '=' || ch2 == '>') {
                token = make_shared<PunctToken>(position, ch1, ch2);
                return true;
            }
            break;
        case '<':
            if (ch2 == '=' || ch2 == '<') {
                token = make_shared<PunctToken>(position, ch1, ch2);
                return true;
            }
            break;
        default:
            break;
    }

    unGetChar();
    position.length--;
    token = make_shared<PunctToken>(position, ch1);
    return true;
}

} // namespace Lexer
